package cn.xiaolang.function.data_structure;

import java.util.LinkedList;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 限流算法-滑动窗口
 * <p>
 * 滑动时间窗口限流实现，假设某个服务最多只能每秒钟处理100个请求，
 * <p>
 * 我们可以设置一个1秒钟的滑动时间窗口，窗口中有10个格子，每个格子100毫秒，每100毫秒移动一次，
 * 每次移动都需要记录当前服务请求的次数
 *
 * @author fuhw/Dean
 * @date 2020-04-10
 */
public class FlowLimitBySlideWindow {


    // 窗口大小
    private int permits;
    // 窗口对应的槽位
    private LinkedList<Long> slots = new LinkedList<>();
    // 计数器
    private AtomicLong counter = new AtomicLong(0L);
    // 槽位数
    private int slotsSize;

    public FlowLimitBySlideWindow(int permits, int slotsSize) {
        this.permits = permits;
        this.slotsSize = slotsSize;
        // 初始化槽位
        for (int i = 0; i < slotsSize; i++) {
            slots.add(0L);
        }
        new Timer().scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                // 滑动
                slots.addLast(counter.get());
                if (slots.size() > slotsSize) {
                    slots.removeFirst();
                }
            }
        }, 1, 1000 / slotsSize);
    }

    private boolean acquire() {
        System.out.println(slots);
        // 窗口首尾槽位之差就是窗口内总的请求数
        if ((slots.peekLast() - slots.peekFirst()) < permits) {
            // 累积请求数
            counter.getAndIncrement();
            return true;
        } else {
            return false;
        }
    }

    public static void main(String[] args) {


        FlowLimitBySlideWindow limit = new FlowLimitBySlideWindow(100, 10);
        AtomicLong successCount = new AtomicLong();
        AtomicLong totalCount = new AtomicLong();
        long start = System.currentTimeMillis();
        while (true) {
            if (limit.acquire()) {
                successCount.getAndIncrement();
            }
            totalCount.getAndIncrement();

            long consume = (System.currentTimeMillis() - start) / 1000L;
            long qps = consume == 0 ? totalCount.get() : totalCount.get() / consume;
            System.out.println("total:" + totalCount.get() +
                    ",success:" + successCount.get() +
                    ",consume:" + consume + "(s)" +
                    ",qps:" + qps + "/s");
        }
    }
}